#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <math.h>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/inotify.h>
#include <linux/limits.h>
#include <sys/poll.h>
#include <linux/input.h>
#include <errno.h>

/*
 *	Multi-touch Protocol has A/B types, this code parse as protocol B
 */

#define MAX_POINT				10
#define EPSILON					0.000001

#define BITS_TO_LONGS(b)		(((b) + 31) >> 5)
#define BIT_DWORD(nr)			((nr) >> 5)
#define BIT_MASK(nr)			(1UL << ((nr) & 0x1F))
#define BIT_TEST(nr, addr)		((addr)[BIT_DWORD(nr)] & BIT_MASK(nr))

struct slot_data {
	int x;
	int y;
	int w;
	int id;
	int d;
};

static struct slot_data s_touch[MAX_POINT];
static int s_idx = 0;	// store current finger index
static int s_cnt = 0;	// store current press finger count
static int d_idx = -1;	// store down/up event index

class CPointF {
public:
	CPointF() : _x(0.f), _y(0.f) { }
	CPointF(const CPointF& p) : _x(p.x()), _y(p.y()) { }
	CPointF(float x, float y) : _x(x), _y(y) { }

	float x() const { return _x; }
	float y() const { return _y; }

private:
	float _x, _y;
};

class CLineF {
public:
	CLineF() { }
	CLineF(CPointF& pt1, CPointF& pt2) : p1(pt1), p2(pt2) { }

	CPointF point1() { return p1; }
	CPointF point2() { return p2; }
	
	float length()
	{
		float dx = p1.x() - p2.x();
		float dy = p1.y() - p2.y();
		return sqrt(dx * dx + dy * dy);
	}

	// -90 ~ 90
	float angle()
	{
		double dx = p2.x() - p1.x();
		double dy = p2.y() - p1.y();
	
		if(dx < EPSILON && dx > -EPSILON)
			return 90.f;
		return atan(dy / dx) * 180.f / M_PI;
	}

private:
	CPointF p1, p2;
};

class CPinchRecognizer {
public:
	enum {
		TP_TouchBegin,
		TP_TouchEnd,
		TP_TouchUpdate,
	};
	
	enum {
		RES_Ignore,
		RES_MayBeGesture,
		RES_CancelGesture,
		RES_FinishGesture,
		RES_TriggerGesture,
	};

	CPinchRecognizer() { reset(); }
	
	void reset()
	{
		startPosition[0] = startPosition[1] = CPointF();
		totalScaleFactor = lastScaleFactor = scaleFactor = 1;
	}
	
	int recognize(int type, CPointF p1, CPointF p2)
	{
		int res = RES_Ignore;
		switch(type) {
		case TP_TouchBegin:
			res = RES_MayBeGesture;
			startPosition[0] = p1;
			startPosition[1] = p2;
			break;
		case TP_TouchEnd:
			if(res == RES_MayBeGesture)
				res = RES_CancelGesture;
			else
				res = RES_FinishGesture;

			reset();
			break;
		case TP_TouchUpdate:
		{
			CLineF line(p1, p2);
			CLineF lastLine(startPosition[0], startPosition[1]);
	
			float newScaleFactor = line.length() / lastLine.length();
			
			if (newScaleFactor > 2.5 || newScaleFactor < 0.05)
				return RES_Ignore;
			scaleFactor = newScaleFactor;
			totalScaleFactor = totalScaleFactor * scaleFactor;
			startPosition[0] = p1;
			startPosition[1] = p2;
			
			// TODO:
			printf("Pinch scale = %f, totalscale = %f\n",
				scaleFactor, totalScaleFactor);
			break;
		}
		default:
			break;
		}
		return 0;
	}
private:
	CPointF startPosition[2];

	float scaleFactor;
	float totalScaleFactor;
	float lastScaleFactor;

	float rotationAngle;
	float totalRotationAngle;
	float lastRotationAngle;
};

static bool check_abs_device(int fd)
{
	bool res = false;
	unsigned long evbit[BITS_TO_LONGS(EV_MAX)] = { 0 };
	
	if (ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit) < 0)
		return res;
	
	if(BIT_TEST(EV_ABS, evbit)) {
		int ret = 0;
		struct input_absinfo xinfo = {0};
		struct input_absinfo yinfo = {0};
		
		ret |= ioctl(fd, EVIOCGABS(ABS_X), &xinfo);
		ret |= ioctl(fd, EVIOCGABS(ABS_Y), &yinfo);
		if(ret == 0) {
			printf("   Max ABS_X : %d\n", xinfo.maximum);
			printf("   Max ABS_Y : %d\n", yinfo.maximum);
		}
		
		ret |= ioctl(fd, EVIOCGABS(ABS_MT_POSITION_X), &xinfo);
		ret |= ioctl(fd, EVIOCGABS(ABS_MT_POSITION_Y), &yinfo);
		if(ret == 0) {
			printf("   Max ABS_MT_POSITION_X : %d\n", xinfo.maximum);
			printf("   Max ABS_MT_POSITION_Y : %d\n", yinfo.maximum);
		}

		res = ret == 0;
	}

	return res;
}

int main(int argc, char *argv[])
{
	char devp[256] = "/dev/input/event2";
	if(argc > 1 && !strncmp(argv[1], "/dev/", 5))
		strcpy(devp, argv[1]);

	int fd = open(devp, O_RDWR);
	if(fd < 0) {
		perror("open");
		return 1;
	}

	if(!check_abs_device(fd)) {
		close(fd);
		printf("Not a ABS device.\n");
		return 1;
	}

	for(int i = 0; i < MAX_POINT; ++i)
		s_touch[i].d = 0;

#if 0
	CPointF p1(120, 50);
	CPointF p2(700, 420);
	CLineF line(p1, p2);
	printf(">> length = %f\n", line.length());
#endif

	CPinchRecognizer gesture;

	struct input_event event;
	while (1) {
		int rlen = read(fd, &event, sizeof(struct input_event));
		if(rlen != sizeof(struct input_event)) {
			usleep(5000);
			continue;
		}

		if(event.type == EV_SYN) {
			if(event.code == SYN_REPORT) {

				if(s_touch[s_idx].id == -1) {

					if(s_idx == d_idx) {
						printf("==> [%d]TOUCH UP[%d, %d]\n", s_idx,
							s_touch[s_idx].x, s_touch[s_idx].y);
					}

					s_touch[s_idx].d = 0;
					s_cnt -= 1;
					
					if(s_cnt < 2)
						gesture.recognize(CPinchRecognizer::TP_TouchEnd,
							CPointF(), CPointF());

				} else if(s_touch[s_idx].d == 0) {
					
					if(s_cnt >= (MAX_POINT - 1))
						continue;
					
					s_touch[s_idx].d = 1;
					s_cnt += 1;
					
					if(s_cnt == 1) {
						printf("==> [%d]TOUCH DOWN[%d, %d]\n", s_idx,
							s_touch[s_idx].x, s_touch[s_idx].y);
						d_idx = s_idx;
					} else if(s_cnt == 2) {
						CPointF p1(s_touch[0].x, s_touch[0].y);
						CPointF p2(s_touch[1].x, s_touch[1].y);
						gesture.recognize(
							CPinchRecognizer::TP_TouchBegin, p1, p2);
					}
				} else {
					if(s_cnt == 1 && s_idx == d_idx) {
						printf("==> [%d]TOUCH MOVE[%d, %d]\n", s_idx,
							s_touch[s_idx].x, s_touch[s_idx].y);
					} else if(s_cnt >= 2) {
						CPointF p1(s_touch[0].x, s_touch[0].y);
						CPointF p2(s_touch[1].x, s_touch[1].y);
						gesture.recognize(
							CPinchRecognizer::TP_TouchUpdate, p1, p2);
					}
				}
			}
		} else if(event.type == EV_ABS) {
			switch (event.code) {
			case ABS_MT_SLOT:
				s_idx = event.value;
				break;
			case ABS_X:
			case ABS_MT_POSITION_X:
				s_touch[s_idx].x = event.value;
				break;
			case ABS_Y:
			case ABS_MT_POSITION_Y:
				s_touch[s_idx].y = event.value;
				break;
			case ABS_MT_TOUCH_MAJOR:
			case ABS_MT_WIDTH_MAJOR:
				s_touch[s_idx].w = event.value;
				break;
			case ABS_MT_TRACKING_ID:
				s_touch[s_idx].id = event.value;
				break;
			default:
				break;
			}
		}
	}
	close(fd);
	return 0;
}
